package com.biz.primus.ms.oms.service.delivery;

import com.biz.primus.base.exception.BizSilentException;
import com.biz.primus.base.vo.PageResult;
import com.biz.primus.common.utils.Copier;
import com.biz.primus.model.oms.enums.*;
import com.biz.primus.model.oms.vo.OmsTimeoutRecordVo;
import com.biz.primus.model.oms.vo.TimeoutRecordHandleRequest;
import com.biz.primus.model.oms.vo.TimeoutRecordQueryVo;
import com.biz.primus.ms.base.service.AbstractBaseService;
import com.biz.primus.ms.oms.dao.po.OmsOrder;
import com.biz.primus.ms.oms.dao.po.OrderTimeoutRecord;
import com.biz.primus.ms.oms.dao.redis.FlashOrderMonitoringRedisDao;
import com.biz.primus.ms.oms.dao.repository.OrderTimeoutRecordRepository;
import com.biz.primus.model.oms.exception.OmsExceptions;
import com.biz.primus.ms.oms.service.core.OrderStateService;
import com.biz.primus.ms.oms.vo.OmsOrderResource;
import com.biz.primus.ms.oms.vo.OmsOrderResource.OmsOrderResourceImpl;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;

import javax.persistence.criteria.Predicate;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 闪送订单监控
 * @author yanweijin
 * @date 2017/11/24
 */
@Service
@Slf4j
public class FlashDeliveryMonitoringServiceImpl extends AbstractBaseService implements DeliveryMonitoringService {

	@Autowired
	private FlashOrderMonitoringRedisDao monitoringRedisDao;

	@Autowired
	private OrderStateService orderStateService;

	@Autowired
	private OrderTimeoutRecordRepository timeoutRecordRepository;

	@Override
	public void filterMonitoredOrder(OmsOrderResource resource) {
		OmsOrder order = orderStateService.findOrder(resource);
		if(needMonitoring(order)){
			monitoringRedisDao.addMonitoringSource(order.getId(), order.getExpectedReceivingTime());
		}
	}

	@Override
	public void scanMonitoredSource(boolean useJpaQuery){
		List<OrderTimeoutRecord> records = this.popTimeoutSource(useJpaQuery).stream()
				.map(OmsOrderResourceImpl::new)
				.map(orderStateService::findOrder)
				.filter(this::needRecordTimeout)
				.map(this::toTimeoutRecord)
				.collect(Collectors.toList());
		timeoutRecordRepository.save(records);
	}

	@Override
	public PageResult<OmsTimeoutRecordVo> queryTimeoutRecords(TimeoutRecordQueryVo query) {
		if(query.getStateEq()==null){
			throw new IllegalArgumentException("要查询的状态为null");
		}
		PageRequest pageRequest = query.toPageRequest(new Sort(Sort.Direction.DESC,"expectedTime"));
		Page<OrderTimeoutRecord> records = timeoutRecordRepository.findAll((root, cq, cb) -> {
			List<Predicate> predicates = Lists.newArrayList();
			predicates.add(cb.equal(root.get("state").as(TimeoutHandleState.class), query.getStateEq()));
			if (StringUtils.isNotBlank(query.getOrderCodeLike())) {
				predicates.add(cb.like(root.get("orderCode").as(String.class), query.getOrderCodeLike()));
			}
			return cb.and(predicates.stream().toArray(Predicate[]::new));
		}, pageRequest);
		List<OmsTimeoutRecordVo> result = records.getContent().stream()
				.map(po -> Copier.from(po).to(new OmsTimeoutRecordVo())).collect(Collectors.toList());
		return PageResult.of(records.getNumber(),records.getTotalPages(),result);
	}

	@Override
	public void handleRecord(TimeoutRecordHandleRequest req) {
		switch (req.getAction()) {
			case begin:
				beginHandle(req);
				break;
			case cancel:
				cancelHandle(req);
				break;
			case finish:
				finishHandle(req);
				break;
			default:
				throw new IllegalArgumentException("无法识别的action:"+req.getAction());
		}
	}

	private void beginHandle(TimeoutRecordHandleRequest req){
		Long recordId = Long.valueOf(req.getRecordIdString());
		OrderTimeoutRecord record = timeoutRecordRepository.findOne(recordId);
		if(record.getState()!=TimeoutHandleState.for_process){
			throw new BizSilentException(OmsExceptions.IS_HANDING);
		}
		record.setState(TimeoutHandleState.processing);
		record.setHandler(req.getOperator());
		try{
			//save and flush 如果出现乐观锁异常,则转换为"业务处理中"
			timeoutRecordRepository.saveAndFlush(record);
		}catch (OptimisticLockingFailureException e){
			throw new BizSilentException(OmsExceptions.IS_HANDING);
		}
	}

	private void cancelHandle(TimeoutRecordHandleRequest req){
		Long recordId = Long.valueOf(req.getRecordIdString());
		OrderTimeoutRecord record = timeoutRecordRepository.findOne(recordId);
		if(record.getState()!=TimeoutHandleState.processing){
			throw new BizSilentException(OmsExceptions.STATE_ERROR);
		}
		if(!Objects.equals(record.getHandler(),req.getOperator())){
			throw new BizSilentException(OmsExceptions.IS_HANDING);
		}
		record.setHandler(null);
		record.setState(TimeoutHandleState.for_process);
		try{
			//save and flush 如果出现乐观锁异常,则转换为"业务处理中"
			timeoutRecordRepository.saveAndFlush(record);
		}catch (OptimisticLockingFailureException e){
			throw new BizSilentException(OmsExceptions.IS_HANDING);
		}
	}

	private void finishHandle(TimeoutRecordHandleRequest req){
		Long recordId = Long.valueOf(req.getRecordIdString());
		OrderTimeoutRecord record = timeoutRecordRepository.findOne(recordId);
		if(record.getState()!=TimeoutHandleState.processing){
			throw new BizSilentException(OmsExceptions.STATE_ERROR);
		}
		if(!Objects.equals(record.getHandler(),req.getOperator())){
			throw new BizSilentException(OmsExceptions.IS_HANDING);
		}
		record.setTimeoutCause(req.getCause());
		record.setHandleResult(req.getHandleResult());
		try{
			//save and flush 如果出现乐观锁异常,则转换为"业务处理中"
			timeoutRecordRepository.saveAndFlush(record);
		}catch (OptimisticLockingFailureException e){
			throw new BizSilentException(OmsExceptions.IS_HANDING);
		}
	}


	/**
	 * 判断订单是否需要被监控
	 * @param order
	 * @return
	 */
	private boolean needMonitoring(OmsOrder order){
		//已取消的订单,不加入监控
		if(order.getCancelType()!= CancelType.none){
			return false;
		}
		//非闪送的订单,不加入监控
		if(order.getDeliveryType()!= OrderDeliveryType.self_now){
			return false;
		}
		//已完成的订单,不加入监控
		if(order.getState() == OrderState.finished){
			return false;
		}
		//期望收货时间为null的订单,不加入监控,并且记录警告日志
		if(order.getExpectedReceivingTime()==null){
			log.warn("oms-monitoring: 闪送订单[{}]没有应有的expectedReceivingTime字段,无法被监控", order.getOrderCode());
			return false;
		}
		return true;
	}

	/**
	 * 判断订单是可以记录超时,由于传入的订单一定是超过expectedTime的,不需要检查时间
	 * @param order
	 * @return
	 */
	private boolean needRecordTimeout(OmsOrder order) {
		//如果是原本就不需要被监控的订单,自然也不需要被记录
		//如果订单被取消,不需要记录
		//如果后台修改了配送方式,不再是自配送,不需要记录
		//如果订单状态走到finished,也不需要记录
		if(!needMonitoring(order)){
			return false;
		}
		//配送状态为配送中之后(不包括配送中)的,已经完成配送,不需要记录超时
		if(order.getDeliveryState().getValue()>OrderDeliveryState.distributing.getValue()){
			return false;
		}
		return true;
	}

	/**
	 * @param useJpaQuery 是否查询数据库来获取id 一般每10次redis查询则使用1次数据库查询,防止redis数据不准
	 * @return 超时的订单id集合
	 */
	private Set<Long> popTimeoutSource(boolean useJpaQuery){
		if(useJpaQuery){
			//todo
		}
		return monitoringRedisDao.popTimeoutedSource();
	}


	private OrderTimeoutRecord toTimeoutRecord(OmsOrder order) {
		OrderTimeoutRecord record = new OrderTimeoutRecord();
		record.setId(idService.getNextId());
		record.setOrderCode(order.getOrderCode());
		record.setExpectedTime(order.getExpectedReceivingTime());
		record.setOrderId(order.getId());
		record.setState(TimeoutHandleState.for_process);
		return record;
	}
}
